深入了解最新的 JavaScript 功能(ES7 到 ES13)

记录一下ES7——ES14的内容

ES7 / ECMAScript 2016

1. 幂运算符

与Math.pow()相同的操作

1
2
2 ** 3 = 8
5 ** 4 = 625

2. Array.prototype.includes()

用于检查数组中是否存在特定值,如果找到匹配项就返回 true 否则返回 false

1
2
3
['a','b','c'].includes('a'); // true
['a','b','c'].includes('d'); // false
['ab','b','c'].includes('a'); // false

3. 函数参数中的尾随逗号

在数组中,我们可以像 [1,2,] 这样使用尾随逗号。 为了保持一致性,现在我们可以在函数参数和参数中使用尾随逗号

1
2
const text = (x,y,z,)=>console.log(x,y,z);
test(1,2,3); // logs: 1 2 3

ES8 / ECMAScript 2017

1. 异步函数

我们可以在函数前面添加 async 关键字使其成为异步函数,然后我们可以在函数里面使用 await 关键字

1
2
3
async function test(){
const result = await fetch();
}

2. padStart与padEnd

可以在字符串的开头和结尾添加任意字符用来制作特定长度的字符串

1
2
3
4
5
6
7
const str = 'hello';
str.padStart(10); // ' hello'
str.padEnd(10); // 'hello '
str.padStart(10,'*'); // '*****hello'
str.padEnd(10,'*'); // 'hello*****'
'2'.padStart(2,'0'); // '02';
```

3. Object.entries()

允许你将一个对象的可枚举属性转换为一个由 [key, value] 对组成的数组

1
2
3
4
5
6
7
const obj = { a: 1, b: 2, c: 3 };

// 将对象的键值对转换为数组
const entries = Object.entries(obj);

console.log(entries);
// 输出:[ ['a', 1], ['b', 2], ['c', 3] ]

4. Object.values()

接受一个对象,返回一个数组,该数组仅包含对象内部的值,用逗号分隔

1
2
3
const obj = {name:'JS', lib: 'Angular'};

console.log(Object.values(obj)); // ['JS','Angular']

5. Object.getOwnPropertyDescriptors()

接收一个对象,返回指定对象所有自身属性(非继承属性)的描述对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const obj = {
foo: 123,
get bar() { return 'abc' }
};

Object.getOwnPropertyDescriptors(obj)
// { foo:
// { value: 123,
// writable: true,
// enumerable: true,
// configurable: true },
// bar:
// { get: [Function: get bar],
// set: undefined,
// enumerable: true,
// configurable: true } }

6. SharedArrayBuffer

允许 Worker 线程与主线程共享同一块内存。SharedArrayBuffer的 API 与 ArrayBuffer一模一样,唯一的区别是后者无法共享数据。

1
2
3
4
5
6
7
8
9
10
// 主线程

// 新建 1KB 共享内存
const sharedBuffer = new SharedArrayBuffer(1024);

// 主线程将共享内存的地址发送出去
w.postMessage(sharedBuffer);

// 在共享内存上建立视图,供写入数据
const sharedArray = new Int32Array(sharedBuffer);

上面代码中,postMessage方法的参数是 SharedArrayBuffer对象。

Worker 线程从事件的 data属性上面取到数据。

1
2
3
4
5
6
7
8
9
10
// Worker 线程
onmessage = function (ev) {
// 主线程共享的数据,就是 1KB 的共享内存
const sharedBuffer = ev.data;

// 在共享内存上建立视图,方便读写
const sharedArray = new Int32Array(sharedBuffer);

// ...
};

共享内存也可以在 Worker 线程创建,发给主线程。

ES9 / ECMAScript 2018

1. 异步遍历器

详细介绍请点击这里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function fetcher(task,time) {
return new Promise(function(res) {
setTimeout(function() {
res(`${task} Done`);
}, time);
})
}

const tasks = [1,2,3,4].map(d => {
return fetcher(d, 1000* d);
});


(async () => {
for await (const value of tasks) {
console.log(value);
}
})();

2. Promise.prototype.finally()

Promise 现在有一个 finally 块,它将在 then or catch 块之后执行

1
task().then().catch().finally()

3. 对象的扩展运算符

...运算符可以作用在对象上了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 剩余参数 
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x; // 1
y; // 2
z; // {a:3,b:4};

// 展开属性
let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }

let foo = { ...['a', 'b', 'c'] };
foo
// {0: "a", 1: "b", 2: "c"}

ES10 / ECMAScript 2019

1. Array.prototype.flat()

flat()方法创建一个新数组,用于将嵌套的数组“拉平”,变成一维的数组。如果没有提供深度,则默认为1。

1
2
3
4
5
6
7
8
9
10
11
12
13
const nestedArray = [1, [2, [3, 4, [5]]]];

const flatArray = nestedArray.flat();

console.log(flatArray); // [1, 2, [3, 4, [5]]]

// 传入参数
const deeplyFlatArray = nestedArray.flat(2);

console.log(deeplyFlatArray); // [1, 2, 3, 4, [5]]

// 传入Infinity不管多少层嵌套都会被转换
nestedArray.flat(Infinity); // [1, 2, 3, 4, 5];

2. Array.prototype.flatMap()

flatMap()方法对原数组的每个成员执行一个函数(相当于执行 Array.prototype.map()),然后对返回值组成的数组执行 flat()方法。返回一个新数组,不会影响原数组

1
2
3
// 相当于 [[2, 4], [3, 6], [4, 8]].flat()
[2, 3, 4].flatMap((x) => [x, x * 2])
// [2, 4, 3, 6, 4, 8]

flatMap()只能展开一层数组。

1
2
3
// 相当于 [[[2]], [[4]], [[6]], [[8]]].flat()
[1, 2, 3, 4].flatMap(x => [[x * 2]])
// [[2], [4], [6], [8]]

3. Object.fromEntries()

Object.fromEntries()方法是 Object.entries()的逆操作,用于将一个键值对数组转为对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Object.fromEntries([
['foo', 'bar'],
['baz', 42]
])
// { foo: "bar", baz: 42 }

const entries = new Map([
['foo', 'bar'],
['baz', 42]
]);

Object.fromEntries(entries)
// { foo: "bar", baz: 42 }

Object.fromEntries(new URLSearchParams('foo=bar&baz=qux'))
// { foo: "bar", baz: "qux" }

4. String.prototype.trimStart()与String.prototype.trimEnd()

它们的行为与 trim()一致,trimStart()消除字符串头部的空格,trimEnd()消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。

1
2
3
4
5
const s = '  abc  ';

s.trim() // "abc"
s.trimStart() // "abc "
s.trimEnd() // " abc"

5. Symbol.prototype.description

返回一个字符串,内容为创建 Symbol时的描述

1
2
3
const sym = Symbol('foo');

sym.description // "foo"

如果在创建的时候没有提供说明则为 undefined

1
2
3
const sym = Symbol();

sym.description // undefined

ES11 / ECMA Script 2020

1. 类的私有变量

通过使用 # 我们现在可以在类中拥有私有变量。

1
2
3
4
5
6
7
8
9
10
11
12
class Person {
#name = '张三';

getName() {
return this.#name;
}
}

const person = new Person();

console.log(person.getName()); // 张三
console.log(person.#name); // 报错

注意,从 Chrome 111 开始,开发者工具里面可以读写私有属性,不会报错,原因是 Chrome 团队认为这样方便调试。### 2. 静态方法与静态属性

类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上 static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。

静态属性指的是 Class 本身的属性,即 Class.propName,而不是定义在实例对象(this)上的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Person {
static name = '张三';

getName() {
return Person.name;
}

static greet() {
return 'hello';
}
}

Person.greet(); // 'hello'
Person.name; // 张三

const person = new Person();
person.greet(); // TypeError: person.greetis not a function
person.name; // undefined

3. Promise.allSettled()

用于处理多个 Promise,与 Promise.all不同在于 Promise.all只要有一个 Promise请求失败它就会报错,而不管其他的请求是否结束,但是 Promise,allSettled会等待所有的请求都结束哩再返回结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const promises = [
Promise.resolve('Resolved Promise 1'),
Promise.reject('Rejected Promise 2'),
Promise.resolve('Resolved Promise 3'),
];

Promise.allSettled(promises)
.then((results) => {
results.forEach((result) => {
if (result.status === 'fulfilled') {
console.log('Fulfilled:', result.value);
} else if (result.status === 'rejected') {
console.log('Rejected:', result.reason);
}
});
})
.catch((error) => {
console.error('Error in Promise.allSettled:', error);
});

4. 可选链(?.)

用于处理你想要访问嵌套对象的属性或者调用方法时,但不确定属性或者对象是否存在的情况。有助于避免嵌套对象为null或者undefind的情况。

1
2
3
4
5
6
7
8
9
10
11
const user = {
id: 1,
name: '张三',
address: {
city: 'New York',
postalCode: '10001',
},
};

user.address.postalCode; // 如果address为 null 或者 undefined 则会报错
user.address?.postalCode; // 如果address为 null 或者 undefined 则为undefined

5. 空值合并(??)

空值合并运算符 (??) 在逻辑运算中用于处理 nullundefined的情况。它提供了一种更安全、更方便的方式来为变量提供默认值。

相比于或运算符(||)来说会更加严格,或运算符是判断是否为假值,而空值合并运算符只会判断是null或者undefined

1
2
3
const headerText = response.settings.headerText || 'Hello, world!';
const animationDuration = response.settings.animationDuration || 300;
const showSplashScreen = response.settings.showSplashScreen || true;

上面的三行代码都通过||运算符指定默认值,但是这样写是错的。开发者的原意是,只要属性的值为nullundefined,默认值就会生效,但是属性的值如果为空字符串或false0,默认值也会生效

1
2
3
const headerText = response.settings.headerText ?? 'Hello, world!';
const animationDuration = response.settings.animationDuration ?? 300;
const showSplashScreen = response.settings.showSplashScreen ?? true;

上面代码中,默认值只有在左侧属性值为nullundefined时,才会生效

6. 动态导入

允许你在运行时有条件或按需导入模块,使用 import() 函数实现,该函数返回一个解析为模块命名空间对象的 Promise

1
2
3
4
5
6
7
8
9
10
mathModulePromise.then((mathModule) => {
const result1 = mathModule.add(5, 3);
const result2 = mathModule.subtract(8, 4);

console.log('Result of addition:', result1);
console.log('Result of subtraction:', result2);
})
.catch((error) => {
console.error('Error during dynamic import:', error);
});

7. BigInt

允许您处理超出标准 Number 类型限制的非常大的整数的数据类型。详细介绍看这里

8. globalThis

globalThis 是一个 JavaScript 全局对象,它提供了在任何环境(包括浏览器和 Node.js)中访问全局对象的标准化方法。具体的全局对象根据 JavaScript 代码运行的上下文而变化:

  • 在浏览器环境中,全局对象是window
  • 在 Node.js 环境中,全局对象是 global

globalThis 提供了跨不同 JavaScript 环境访问全局对象的一致方式,使代码更加可移植并避免特定于环境的代码。

ES12 / ECMA Script 2021

1. String.prototype.replaceAll()

用来替换字符串上所有出现的特定字符串,返回一个新字符串,不会影响原始字符串

1
2
3
const str = 'hello world,hello everyone';
const newStr = str.replaceAll('hello','bye');
newStr;// bye world,bye everyone

2.Promise.any

该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。

只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态。如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。

Promise.any()Promise.race()方法很像,只有一点不同,就是Promise.any()不会因为某个 Promise 变成rejected状态而结束,必须等到所有参数 Promise 变成rejected状态才会结束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => resolve('Promise 1 resolved'), 1000);
});

const promise2 = new Promise((resolve, reject) => {
setTimeout(() => reject('Promise 2 rejected'), 500);
});

const promise3 = new Promise((resolve, reject) => {
setTimeout(() => resolve('Promise 3 resolved'), 1500);
});

Promise.any([promise1, promise2, promise3])
.then(result => {
console.log('Fulfilled:', result);
})
.catch(error => {
console.log('Rejected:', error);
});

3. WeakRef

WeakRef对象允许你保留对另一个对象的弱引用,而不会阻止被弱引用对象被 GC 回收。WeakRef 的主要用例是通过允许对象在不再使用时进行垃圾收集来避免内存泄漏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const obj = { data: 'example' };
const weakRef = new WeakRef(obj);

const objFromWeakRef = weakRef.deref();
console.log(objFromWeakRef); // { data: 'example' }


let weakRef;

{
const localObj = { data: 'local' };
weakRef = new WeakRef(localObj);
}
const retrievedObj = weakRef.deref();
console.log(retrievedObj); // undefined 因为localObj被回收了

4. &&=, ||=??=

这三个运算符||=&&=??=相当于先进行逻辑运算,然后根据运算结果,再视情况进行赋值运算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 或赋值运算符
x ||= y
// 等同于
x || (x = y)

// 与赋值运算符
x &&= y
// 等同于
x && (x = y)

// Null 赋值运算符
x ??= y
// 等同于
x ?? (x = y)

它们的一个用途是,为变量或属性设置默认值。

1
2
3
4
5
// 老的写法
user.id = user.id || 1;

// 新的写法
user.id ||= 1;

5. 数字分隔符( _ )

允许你在数字文字中使用下划线 ( _ ) 作为分隔符以提高可读性。

1
const nub = 1_000_000_000;

ES13 / ECMA Script 2022

1. 顶层 await

它允许你在模块的顶级使用 await 关键字。传统上,await 关键字只能在 async 函数内使用,但对于顶级 await,你可以直接在模块的顶层使用它。具体的详细列子建议看这里

2. at()

它允许你访问String中指定索引处的字符或Array中指定索引处的元素。at()的主要功能在于它支持负索引,从而更容易从String或者Array末尾访问元素。

1
2
3
4
5
6
7
8
9
10
11
const str = 'Hello, World!';

console.log(str.at(1)); // "e"
console.log(str.at(-1)); // "!"
console.log(str.at(1000)); // undefined

const arr = ['张三', '李四', '王五'];

console.log(arr.at(0)); // "张三"
console.log(arr.at(-1)); // "王五"
console.log(arr.at(5)); // undefined

3. Object.hasOwn

JavaScript对象的属性分成两种:自身的属性和继承的属性。对象实例有一个hasOwnProperty()方法,可以判断某个属性是否为原生属性。

Object.hasOwn()可以接受两个参数,第一个是所要判断的对象,第二个是属性名。

1
2
3
4
5
const foo = Object.create({ a: 123 });
foo.b = 456;

Object.hasOwn(foo, 'a') // false
Object.hasOwn(foo, 'b') // true

上面示例中,对象foo的属性a是继承属性,属性b是原生属性。Object.hasOwn()对属性a返回false,对属性b返回true

4. 类的字段声明

在 ES2022 之前,类字段只能在构造函数中声明。

1
2
3
4
5
6
7
8
9
10
class Car {
constructor() {
this.color = 'blue';
this.age = 2;
}
}

const car = new Car();
console.log(car.color); // blue
console.log(car.age); // 2

ES2022 消除了此限制。现在我们可以编写这样的代码

1
2
3
4
5
6
7
8
class Car {
color = 'blue';
age = 2;
}

const car = new Car();
console.log(car.color); // blue
console.log(car.age); // 2

5. Array.prototype.findLast() 跟 Array.prototype.findLast()

find()findIndex()都是从数组的0号位,依次向后检查。findLast()findLastIndex(),从数组的最后一个成员开始,依次向前检查,其他都保持不变。

1
2
3
4
5
6
7
8
9
const array = [
{ value: 1 },
{ value: 2 },
{ value: 3 },
{ value: 4 }
];

array.findLast(n => n.value % 2 === 1); // { value: 3 }
array.findLastIndex(n => n.value % 2 === 1); // 2

ES14 / ECMAScript 2023

1. toReversed(),toSorted(),toSpliced(),with()

很多数组的传统方法会改变原数组,比如push()pop()shift()unshift()等等。数组只要调用了这些方法,它的值就变了。而toReversed()toSorted()toSpliced()with() 对数组进行操作时不会改变原数组,而是返回一个原数组的拷贝

  • Array.prototype.toReversed() -> Array
  • Array.prototype.toSorted(compareFn) -> Array
  • Array.prototype.toSpliced(start, deleteCount, ...items) -> Array
  • Array.prototype.with(index, value) -> Array

它们分别对应数组的原有方法。

  • toReversed()对应reverse(),用来颠倒数组成员的位置。
  • toSorted()对应sort(),用来对数组成员排序。
  • toSpliced()对应splice(),用来在指定位置,删除指定数量的成员,并插入新成员。
  • with(index, value)对应splice(index, 1, value),用来将指定位置的成员替换为新的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const sequence = [1, 2, 3];
sequence.toReversed() // [3, 2, 1]
sequence // [1, 2, 3]

const outOfOrder = [3, 1, 2];
outOfOrder.toSorted() // [1, 2, 3]
outOfOrder // [3, 1, 2]

const array = [1, 2, 3, 4];
array.toSpliced(1, 2, 5, 6, 7) // [1, 5, 6, 7, 4]
array // [1, 2, 3, 4]

const correctionNeeded = [1, 1, 3];
correctionNeeded.with(1, 2) // [1, 2, 3]
correctionNeeded // [1, 1, 3]

2. Symbols 可以用作 WeakMap 的key

3. #!命令

具体看这